#include <iostream>
#include <map>
#include <cstring>
#include <array>
#include <chrono>

#include <glm/mat4x4.hpp>

#include "VulkanRenderer.h"
#include "Texture.h"
#include "MemoryTool.h"
#include "CommandTool.h"

namespace namespace_easy_car_ui
{

Camera::Camera()
{
    m_posistion = glm::vec3(0.0f, 0.0f, 2.0f);
    glm::vec3  direction {};
    direction.x = cos(glm::radians(m_pitch)) * cos(glm::radians(m_yaw));
    direction.y = sin(glm::radians(m_pitch));
    direction.z = cos(glm::radians(m_pitch)) * sin(glm::radians(m_yaw));

    m_face = glm::normalize(direction);

    m_up = glm::vec3(0.0f, 1.0f, 0.0f);
}

Camera::~Camera()
{

}


void Camera::UpdatePositon(float deltaSeconds)
{
    if (true == m_isForward)
    {
        m_posistion += m_face * m_speed;
    }
    if (true == m_isRetreated)
    {
        m_posistion -= m_face * m_speed;
    }
    if (true == m_isLeftwards)
    {
        m_posistion -= glm::normalize(glm::cross(m_face, m_up)) * m_speed;
    }
    if (true == m_isRightwards)
    {
        m_posistion += glm::normalize(glm::cross(m_face, m_up)) * m_speed;
    }
}

void Camera::UpdateFace(float deltaYaw, float deltaPitch)
{
    m_deltaYaw = deltaYaw;
    m_deltaPitch = deltaPitch;

    float tmpPitch = m_pitch + m_deltaPitch;

    if (tmpPitch > 89.0f)
    {
        tmpPitch = 89.0f;
    }
    if (tmpPitch < -89.0f)
    {
        tmpPitch = -89.0f;
    }

    glm::vec3  direction {};
    direction.x = cos(glm::radians(tmpPitch)) * cos(glm::radians(m_yaw + m_deltaYaw));
    direction.y = sin(glm::radians(tmpPitch));
    direction.z = cos(glm::radians(tmpPitch)) * sin(glm::radians(m_yaw + m_deltaYaw));

    m_face = glm::normalize(direction);
}

void Camera::CommitFace(float deltaYaw, float deltaPitch)
{
    m_yaw += deltaYaw;
    m_pitch += deltaPitch;

    if (m_pitch > 89.0f)
    {
        m_pitch = 89.0f;
    }
    if (m_pitch < -89.0f)
    {
        m_pitch = -89.0f;
    }

    glm::vec3  direction {};
    direction.x = cos(glm::radians(m_pitch)) * cos(glm::radians(m_yaw));
    direction.y = sin(glm::radians(m_pitch));
    direction.z = cos(glm::radians(m_pitch)) * sin(glm::radians(m_yaw));

    m_face = glm::normalize(direction);

}

VulkanRenderer::VulkanRenderer()
{

}

VulkanRenderer::~VulkanRenderer()
{

}

VulkanRenderer& VulkanRenderer::GetInstance()
{
    static VulkanRenderer instance;
    return instance;
}

//https://registry.khronos.org/vulkan/specs/latest/html/vkspec.html#PFN_vkDebugUtilsMessengerCallbackEXT
static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(
    VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
    VkDebugUtilsMessageTypeFlagsEXT messageType,
    const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData,
    void* pUserData)
{
    std::cout << "DEBUG Message: " << pCallbackData->pMessage << std::endl;
    return VK_FALSE;
}

void VulkanRenderer::Initialize(const bool enableDebug, uint32_t width, uint32_t height)
{
    m_enableDebug = enableDebug;
    m_width = width;
    m_height = height;
    GetAllSupportedLayers();
    CreateVkInstance();
    SetDebugUtilMessenger();
    EnumeratePhysicalDevices();
    SeletPhysicalDevice();
    CreateDevice();
    CreateRenderPass();
    CreateDepthResources();
    CreateFramebuffers();
    m_pipeline.Initialize(
        "./shaders/vert.spv", 
        "./shaders/frag.spv",
        m_device,
        m_renderPass);
    CreateCommandPool();
    CreateCommandBuffer();
    m_texture = std::make_shared<Texture>(
        m_selectedPhysicalDevice->GetHandle(),
        m_device,
        m_commandPool,
        m_graphicsQueue);
    m_texture->SetImagePath("./textures/material.jpg");
    CreateSyncObjects();
    CreatePresentBuffer();
    CreateAndMapUniformBuffer();
    CreateDescriptorPool();
    CreateDescriptorSets();
}

void VulkanRenderer::Deinitialize()
{
    vkDestroyImage(m_device, m_depthImage, nullptr);
    vkDestroyImageView(m_device, m_depthImageView, nullptr);
    vkFreeMemory(m_device, m_depthImageMemory, nullptr);
    m_texture.reset();
    vkDestroyDescriptorPool(m_device, m_descriptorPool, nullptr);
    vkDestroyBuffer(m_device, m_uniformBuffer, nullptr);
    vkFreeMemory(m_device, m_uniformBuffersMemory, nullptr);
    m_vkNodes.clear();
    m_models.clear();
    vkDestroyBuffer(m_device, m_presentBuffer, nullptr);
    vkFreeMemory(m_device, m_presentImageMemory, nullptr);
    vkDestroyFence(m_device, m_fence, nullptr);
    vkDestroyCommandPool(m_device, m_commandPool, nullptr);
    m_pipeline.Deinitialize();
    vkDestroyImage(m_device, m_colorImage, nullptr);
    vkDestroyImageView(m_device, m_colorImageView, nullptr);
    vkFreeMemory(m_device, m_colorImageMemory, nullptr);
    vkDestroyFramebuffer(m_device, m_framebuffer, nullptr);
    vkDestroyRenderPass(m_device, m_renderPass, nullptr);
    vkDestroyDevice(m_device, nullptr);
    m_selectedPhysicalDevice.reset();
    m_physicalDevices.clear();
    if (true == m_enableDebug)
    {
        //https://registry.khronos.org/vulkan/specs/latest/html/vkspec.html#vkDestroyDebugUtilsMessengerEXT
        auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(m_vkInstance, "vkDestroyDebugUtilsMessengerEXT");
        if (nullptr != func)
        {
            func(m_vkInstance, m_debugUtilsMessenger, nullptr);
        }
    }
    vkDestroyInstance(m_vkInstance, nullptr);
    m_supportedLayerNames.clear();
}

void VulkanRenderer::RenderOneFrame(uint32_t* pixels, const float seconds)
{
    // std::cout << std::endl;
    // std::cout << std::endl;
    // std::cout << std::endl;
    // std::cout << ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" << std::endl;

    vkResetCommandBuffer(m_commandBuffer, /*VkCommandBufferResetFlagBits*/ 0);
    RecordCommandBuffer();

    //https://registry.khronos.org/vulkan/specs/latest/html/vkspec.html#submitInfo
    VkSubmitInfo submitInfo{};
    submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
    submitInfo.commandBufferCount = 1;
    submitInfo.pCommandBuffers = &m_commandBuffer;

    if (vkQueueSubmit(m_graphicsQueue, 1, &submitInfo, m_fence) != VK_SUCCESS) {
        throw std::runtime_error("failed to submit draw command buffer!");
    }

    //https://registry.khronos.org/vulkan/specs/latest/html/vkspec.html#vkWaitForFences
    vkWaitForFences(m_device, 1, &m_fence, VK_TRUE, UINT64_MAX);
    vkResetFences(m_device, 1, &m_fence);

    UpdateUniformBuffer(1);

    VkCommandBuffer singleCommandBuffer = CommandTool::BeginSingleTimeCommands(m_device, m_commandPool);

    VkImageMemoryBarrier barrier = {};
    barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
    barrier.oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; // 或当前布局
    barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
    barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
    barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
    barrier.image = m_colorImage; // 你的图像
    barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
    barrier.subresourceRange.baseMipLevel = 0;
    barrier.subresourceRange.levelCount = 1;
    barrier.subresourceRange.baseArrayLayer = 0;
    barrier.subresourceRange.layerCount = 1;

    vkCmdPipelineBarrier(singleCommandBuffer,
                     VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
                     VK_PIPELINE_STAGE_TRANSFER_BIT,
                     0,
                     0, nullptr, 0, nullptr, 1, &barrier);

    VkBufferImageCopy region = {};
    region.bufferOffset = 0;
    region.bufferRowLength = 0;
    region.bufferImageHeight = 0;
    region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
    region.imageSubresource.mipLevel = 0;
    region.imageSubresource.baseArrayLayer = 0;
    region.imageSubresource.layerCount = 1;
    region.imageOffset = {0, 0, 0};
    region.imageExtent = {m_width, m_height, 1};

    vkCmdCopyImageToBuffer(singleCommandBuffer, m_colorImage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, m_presentBuffer, 1, &region);

    CommandTool::EndSingleTimeCommands(m_device, m_commandPool, singleCommandBuffer, m_graphicsQueue);


    void* gpuBuffer = nullptr;
    //https://registry.khronos.org/vulkan/specs/latest/html/vkspec.html#vkMapMemory
    VkResult mapRet = vkMapMemory(m_device, m_presentImageMemory, 0, m_width * m_height * 4,  0, &gpuBuffer);
    if (VK_SUCCESS != mapRet)
    {
        throw std::runtime_error("failed to map frame!");
    }

    std::memcpy(pixels, gpuBuffer, m_width * m_height * 4);
    vkUnmapMemory(m_device, m_presentImageMemory);

    // std::cout << "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" << std::endl;
    // std::cout << std::endl;
    // std::cout << std::endl;
    // std::cout << std::endl;
}

void VulkanRenderer::AddModel(const Model::Ptr& model)
{
    m_models.push_back(model);
}

void VulkanRenderer::PrintPhysicalDevices()
{
    int index {0};
    for (auto physicalDevice : m_physicalDevices)
    {
        std::cout << "Physical Device " << index << " is " << physicalDevice->GetName() <<
                    " Support API Version is " << physicalDevice->GetSupportedVulkanVersion() << std::endl;
        ++index;
    }
}

void VulkanRenderer::PrintSelectedPhysicalDevice()
{
    std::cout << "Selected physical device is " << m_selectedPhysicalDevice->GetName() << std::endl;
}

void VulkanRenderer::GetAllSupportedLayers()
{
    uint32_t layerCount;
    vkEnumerateInstanceLayerProperties(&layerCount, nullptr);

    std::vector<VkLayerProperties> availableLayers(layerCount);
    vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data());

    for (uint32_t i = 0; i < layerCount; ++i)
    {
        m_supportedLayerNames.insert(std::string(availableLayers[i].layerName));
    }
}

void VulkanRenderer::CreateVkInstance()
{
    //https://registry.khronos.org/vulkan/specs/latest/html/vkspec.html#VkApplicationInfo
    VkApplicationInfo appInfo{};
    appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
    appInfo.pNext = nullptr;
    appInfo.pApplicationName = "EasyCarUI";
    appInfo.applicationVersion = VK_MAKE_VERSION(0, 0, 1);
    appInfo.pEngineName = "EasyCarUIEngine";
    appInfo.engineVersion = VK_MAKE_VERSION(0, 0, 1);
    appInfo.apiVersion = VK_API_VERSION_1_0;

    std::vector<const char*> expectedLayerNames = {};
    std::vector<const char*> enableExtensionNames = {};
    if (true == m_enableDebug)
    {
        //https://registry.khronos.org/vulkan/specs/latest/html/vkspec.html#VkDebugUtilsMessengerCreateInfoEXT
        m_debugUtilsMessengerCreateInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
        m_debugUtilsMessengerCreateInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
        m_debugUtilsMessengerCreateInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
        m_debugUtilsMessengerCreateInfo.pfnUserCallback = debugCallback;

        if (m_supportedLayerNames.end() != m_supportedLayerNames.find(std::string("VK_LAYER_KHRONOS_validation")))
        {
            expectedLayerNames.push_back("VK_LAYER_KHRONOS_validation");
        }
    }

    //https://registry.khronos.org/vulkan/specs/latest/html/vkspec.html#VkInstanceCreateInfo
    VkInstanceCreateInfo createInfo{};
    createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
    createInfo.pNext = nullptr;
    if (true == m_enableDebug)
    {
        enableExtensionNames.push_back("VK_EXT_debug_utils");
        createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &m_debugUtilsMessengerCreateInfo;
    }
    createInfo.pApplicationInfo = &appInfo;
    createInfo.enabledLayerCount = static_cast<uint32_t>(expectedLayerNames.size());
    createInfo.ppEnabledLayerNames = expectedLayerNames.data();
    createInfo.enabledExtensionCount = static_cast<uint32_t>(enableExtensionNames.size());;
    createInfo.ppEnabledExtensionNames = enableExtensionNames.data();
    auto ret = vkCreateInstance(&createInfo, nullptr, &m_vkInstance);
    if (VK_SUCCESS != ret)
    {
        throw std::runtime_error("To create VkInstance is failed"); 
    }
}

void VulkanRenderer::SetDebugUtilMessenger()
{
    if (false == m_enableDebug)
    {
        return;
    }

    //https://registry.khronos.org/vulkan/specs/latest/html/vkspec.html#vkGetInstanceProcAddr
    auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(m_vkInstance, "vkCreateDebugUtilsMessengerEXT");
    if (nullptr == func)
    {
        return;
    }

    if (func(m_vkInstance, &m_debugUtilsMessengerCreateInfo, nullptr, &m_debugUtilsMessenger) != VK_SUCCESS) {
        throw std::runtime_error("failed to set up debug messenger!");
    }
}

void VulkanRenderer::EnumeratePhysicalDevices()
{
    //https://registry.khronos.org/vulkan/specs/latest/html/vkspec.html#vkEnumeratePhysicalDevices
    uint32_t physicalDeviceCount{0};
    vkEnumeratePhysicalDevices(m_vkInstance, &physicalDeviceCount, nullptr);
    if (0 == physicalDeviceCount)
    {
        throw std::runtime_error("There is no device support Vulkan!");
    }

    std::vector<VkPhysicalDevice> vkPhysicalDevices(physicalDeviceCount);
    vkEnumeratePhysicalDevices(m_vkInstance, &physicalDeviceCount, vkPhysicalDevices.data());

    for (auto vkPhysicalDevie : vkPhysicalDevices)
    {
        m_physicalDevices.push_back(std::make_shared<PhysicalDevice>(vkPhysicalDevie));
    }
}

void VulkanRenderer::SeletPhysicalDevice()
{
    std::map<uint32_t , PhysicalDevice::Ptr> scoreDevicesMap;

    for (auto physicalDevice : m_physicalDevices)
    {
        if (true == physicalDevice->IsGraphicAbility())
        {
            scoreDevicesMap[physicalDevice->GetScore()] = physicalDevice;
        }
    }

    if (scoreDevicesMap.empty())
    {
        throw std::runtime_error("There is no suitable physical device");
    }

    m_selectedPhysicalDevice = scoreDevicesMap.rbegin()->second;
}

void VulkanRenderer::CreateDevice()
{
    //https://registry.khronos.org/vulkan/specs/latest/html/vkspec.html#VkDeviceQueueCreateInfo
    float queuePriority = 1.0f;
    VkDeviceQueueCreateInfo queueCreateInfo {};
    queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
    queueCreateInfo.pNext = nullptr;
    queueCreateInfo.queueFamilyIndex = m_selectedPhysicalDevice->GetGraphicQueueFamilyIndex();
    queueCreateInfo.queueCount = 1;
    queueCreateInfo.pQueuePriorities = &queuePriority;


    //https://registry.khronos.org/vulkan/specs/latest/html/vkspec.html#VkDeviceCreateInfo
    VkDeviceCreateInfo createInfo{};
    createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
    createInfo.pNext = nullptr;
    createInfo.queueCreateInfoCount = 1;
    createInfo.pQueueCreateInfos = &queueCreateInfo;
    createInfo.enabledExtensionCount = 0;
    createInfo.ppEnabledExtensionNames = nullptr;
    createInfo.pEnabledFeatures = nullptr;

    //https://registry.khronos.org/vulkan/specs/latest/html/vkspec.html#vkCreateDevice
    if (VK_SUCCESS != vkCreateDevice(m_selectedPhysicalDevice->GetHandle(), &createInfo, nullptr, &m_device))
    {
        throw std::runtime_error("To create device is failed");
    }

    //https://registry.khronos.org/vulkan/specs/latest/html/vkspec.html#vkGetDeviceQueue
    vkGetDeviceQueue(m_device, m_selectedPhysicalDevice->GetGraphicQueueFamilyIndex(), 0, &m_graphicsQueue);
}

void VulkanRenderer::CreateRenderPass()
{
    //https://registry.khronos.org/vulkan/specs/latest/html/vkspec.html#VkAttachmentDescription
    VkAttachmentDescription colorAttachment{};
    colorAttachment.format = VK_FORMAT_R8G8B8A8_SRGB;
    colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
    colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
    colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
    colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
    colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
    colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
    colorAttachment.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;

    VkAttachmentDescription depthAttachment{};
    depthAttachment.format = FindDepthFormat();
    depthAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
    depthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
    depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
    depthAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
    depthAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
    depthAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
    depthAttachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;

    //https://registry.khronos.org/vulkan/specs/latest/html/vkspec.html#VkAttachmentReference
    VkAttachmentReference colorAttachmentRef{};
    colorAttachmentRef.attachment = 0;
    colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;

    VkAttachmentReference depthAttachmentRef{};
    depthAttachmentRef.attachment = 1;
    depthAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;

    //https://registry.khronos.org/vulkan/specs/latest/html/vkspec.html#VkSubpassDescription
    VkSubpassDescription subpass{};
    subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
    subpass.colorAttachmentCount = 1;
    subpass.pColorAttachments = &colorAttachmentRef;
    subpass.pDepthStencilAttachment = &depthAttachmentRef;

    //https://registry.khronos.org/vulkan/specs/latest/html/vkspec.html#VkSubpassDependency
    VkSubpassDependency dependency{};
    dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
    dependency.dstSubpass = 0;
    dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT;
    dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT;
    dependency.srcAccessMask = 0;
    dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;

    //https://registry.khronos.org/vulkan/specs/latest/html/vkspec.html#VkRenderPassCreateInfo
    std::array<VkAttachmentDescription, 2> attachments = {colorAttachment, depthAttachment};
    VkRenderPassCreateInfo createInfo{};
    createInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
    createInfo.attachmentCount = static_cast<uint32_t>(attachments.size());;
    createInfo.pAttachments = attachments.data();
    createInfo.subpassCount = 1;
    createInfo.pSubpasses = &subpass;
    createInfo.dependencyCount = 0;
    createInfo.pDependencies = nullptr;

    //https://registry.khronos.org/vulkan/specs/latest/html/vkspec.html#vkCreateRenderPass
    if (VK_SUCCESS != vkCreateRenderPass(m_device, &createInfo, nullptr, &m_renderPass))
    {
        throw std::runtime_error("To create render pass is failed!");
    }
}

void VulkanRenderer::CreateFramebuffers()
{
    //https://registry.khronos.org/vulkan/specs/latest/html/vkspec.html#VkImageCreateInfo
    VkImageCreateInfo imageCreateInfo{};
    imageCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
    imageCreateInfo.pNext = nullptr;
    imageCreateInfo.flags;
    imageCreateInfo.imageType = VK_IMAGE_TYPE_2D;
    imageCreateInfo.format = VK_FORMAT_R8G8B8A8_SRGB; //VK_FORMAT_R8G8B8A8_SRGB
    imageCreateInfo.extent = VkExtent3D{m_width, m_height, 1};
    imageCreateInfo.mipLevels = 1;
    imageCreateInfo.arrayLayers = 1;
    imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
    imageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
    imageCreateInfo.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
    imageCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
    imageCreateInfo.queueFamilyIndexCount = 0;
    imageCreateInfo.pQueueFamilyIndices = nullptr;
    imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
    if (VK_SUCCESS != vkCreateImage(m_device, &imageCreateInfo, nullptr, &m_colorImage))
    {
        throw std::runtime_error("To create image is failed!");
    }

    // https://registry.khronos.org/vulkan/specs/latest/html/vkspec.html#vkGetImageMemoryRequirements
    VkMemoryRequirements memoryRequirements;
    vkGetImageMemoryRequirements(m_device, m_colorImage, &memoryRequirements);

    // https://registry.khronos.org/vulkan/specs/latest/html/vkspec.html#VkMemoryAllocateInfo
    VkMemoryAllocateInfo allocInfo{};
    allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
    allocInfo.allocationSize = memoryRequirements.size;
    m_imageSize = allocInfo.allocationSize;
    allocInfo.memoryTypeIndex = m_selectedPhysicalDevice->FindMemoryType(
        memoryRequirements.memoryTypeBits, 
        VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);

    // https://registry.khronos.org/vulkan/specs/latest/html/vkspec.html#vkAllocateMemory
    if (VK_SUCCESS != vkAllocateMemory(m_device, &allocInfo, nullptr, &m_colorImageMemory))
    {
        throw std::runtime_error("To allocate memory is failed!");
    }

    // https://registry.khronos.org/vulkan/specs/latest/html/vkspec.html#vkBindImageMemory
    if (VK_SUCCESS != vkBindImageMemory(m_device, m_colorImage, m_colorImageMemory, 0))
    {
        throw std::runtime_error("To bind memory is failed!");
    }

    //https://registry.khronos.org/vulkan/specs/latest/html/vkspec.html#VkImageViewCreateInfo
    VkImageViewCreateInfo imageViewCreateInfo{};
    imageViewCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
    imageViewCreateInfo.pNext = nullptr;
    imageViewCreateInfo.flags = 0;
    imageViewCreateInfo.image = m_colorImage;
    imageViewCreateInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
    imageViewCreateInfo.format = VK_FORMAT_R8G8B8A8_SRGB;
    imageViewCreateInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
    imageViewCreateInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
    imageViewCreateInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
    imageViewCreateInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;
    imageViewCreateInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
    imageViewCreateInfo.subresourceRange.baseMipLevel = 0;
    imageViewCreateInfo.subresourceRange.levelCount = 1;
    imageViewCreateInfo.subresourceRange.baseArrayLayer = 0;
    imageViewCreateInfo.subresourceRange.layerCount = 1;

    if (VK_SUCCESS != vkCreateImageView(m_device, &imageViewCreateInfo, nullptr, &m_colorImageView))
    {
        throw std::runtime_error("To create image view is failed!");
    }

    std::array<VkImageView, 2> attachments = {
        m_colorImageView,
        m_depthImageView,
    };

    VkFramebufferCreateInfo framebufferInfo{};
    framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
    framebufferInfo.renderPass = m_renderPass;
    framebufferInfo.attachmentCount = static_cast<uint32_t>(attachments.size());
    framebufferInfo.pAttachments = attachments.data();
    framebufferInfo.width = m_width;
    framebufferInfo.height = m_height;
    framebufferInfo.layers = 1;
    //https://registry.khronos.org/vulkan/specs/latest/html/vkspec.html#vkCreateFramebuffer
    if (VK_SUCCESS != vkCreateFramebuffer(m_device, &framebufferInfo, nullptr, &m_framebuffer)) {
            throw std::runtime_error("To create framebuffer is failed!");
    }
}

void VulkanRenderer::CreateCommandPool()
{
    //https://registry.khronos.org/vulkan/specs/latest/html/vkspec.html#VkCommandPoolCreateInfo
    VkCommandPoolCreateInfo createInfo{};
    createInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
    createInfo.pNext = nullptr;
    createInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
    createInfo.queueFamilyIndex = m_selectedPhysicalDevice->GetGraphicQueueFamilyIndex();

    if (VK_SUCCESS != vkCreateCommandPool(m_device, &createInfo, nullptr, &m_commandPool))
    {
        throw std::runtime_error("To create command pool is failed");
    }
}

void VulkanRenderer::CreateCommandBuffer()
{
    VkCommandBufferAllocateInfo allocInfo{};
    allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
    allocInfo.commandPool = m_commandPool;
    allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
    allocInfo.commandBufferCount = 1;

    if (VK_SUCCESS != vkAllocateCommandBuffers(m_device, &allocInfo, &m_commandBuffer))
    {
        throw std::runtime_error("To allocate command buffers is failed");
    }
}

void VulkanRenderer::CreateSyncObjects()
{
    VkFenceCreateInfo fenceInfo{};
    fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
    fenceInfo.flags = 0;
    //https://registry.khronos.org/vulkan/specs/latest/html/vkspec.html#vkCreateFence
    if (vkCreateFence(m_device, &fenceInfo, nullptr, &m_fence) != VK_SUCCESS)
    {
        throw std::runtime_error("failed to create synchronization objects for a frame!");
    }
}

void VulkanRenderer::CreatePresentBuffer()
{
    VkDeviceSize imageSize = m_width * m_height * 4;
    VkBufferCreateInfo bufferInfo = {};
    bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
    bufferInfo.size = imageSize;
    bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT;
    bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
    if (VK_SUCCESS != vkCreateBuffer(m_device, &bufferInfo, nullptr, &m_presentBuffer))
    {
        throw std::runtime_error("To create buffer of present buffer is failed!");
    }

    VkMemoryRequirements memRequirements;
    vkGetBufferMemoryRequirements(m_device, m_presentBuffer, &memRequirements);


    VkMemoryAllocateInfo allocInfo = {};
    allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
    allocInfo.allocationSize = memRequirements.size;
    allocInfo.memoryTypeIndex = m_selectedPhysicalDevice->FindMemoryType(memRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
    // https://registry.khronos.org/vulkan/specs/latest/html/vkspec.html#vkAllocateMemory
    if (VK_SUCCESS != vkAllocateMemory(m_device, &allocInfo, nullptr, &m_presentImageMemory))
    {
        throw std::runtime_error("To allocate memory of present buffer is failed!");
    }

    // https://registry.khronos.org/vulkan/specs/latest/html/vkspec.html#vkBindImageMemory
    if (VK_SUCCESS != vkBindBufferMemory(m_device, m_presentBuffer, m_presentImageMemory, 0))
    {
        throw std::runtime_error("To bind memory is failed!");
    }
}

void VulkanRenderer::CreateVkNodeByPart(const Part::Ptr part)
{
    for (auto mesh : part->m_meshes)
    {
        VulkanNode::Ptr vkNode = std::make_shared<VulkanNode>(
            m_selectedPhysicalDevice->GetHandle(),
            m_device,
            m_commandPool,
            m_graphicsQueue);
        Node tmp;
        tmp.m_vertes = mesh->m_vertexes;
        tmp.m_indices = mesh->m_vertexesIndices;
        vkNode->CreateIndexAndVertexBuffer(tmp);
        m_vkNodes.push_back(vkNode);
    }

    for (auto childPart : part->m_children)
    {
        CreateVkNodeByPart(childPart.second);
    }
}

void VulkanRenderer::RecordOnVkNodeCommandBuffer(VulkanNode::Ptr vkNode)
{
    VkBuffer vertexBuffers[] = {vkNode->GetVertexBuffer()};
    VkDeviceSize offsets[] = {0};
    vkCmdBindVertexBuffers(m_commandBuffer, 0, 1, vertexBuffers, offsets);
    vkCmdBindIndexBuffer(m_commandBuffer, vkNode->GetIndexBuffer(), 0, VK_INDEX_TYPE_UINT32);

    vkCmdBindDescriptorSets(m_commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipeline.GetLayout(), 0, 1, &m_descriptorSet, 0, nullptr);

    vkCmdDrawIndexed(m_commandBuffer, vkNode->GetNumIndics(), 1, 0, 0, 0);
    // vkCmdDrawIndexed(m_commandBuffer, 3, 1, 0, 0, 0);
}


void VulkanRenderer::RecordCommandBuffer()
{
    VkCommandBufferBeginInfo beginInfo{};
    beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;

    if (vkBeginCommandBuffer(m_commandBuffer, &beginInfo) != VK_SUCCESS) {
        throw std::runtime_error("failed to begin recording command buffer!");
    }

    VkRenderPassBeginInfo renderPassInfo{};
    renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
    renderPassInfo.renderPass = m_renderPass;
    renderPassInfo.framebuffer = m_framebuffer;
    renderPassInfo.renderArea.offset = {0, 0};
    renderPassInfo.renderArea.extent = VkExtent2D{m_width, m_height};

    std::array<VkClearValue, 2> clearValues{};
    clearValues[0].color = {{0.0f, 0.0f, 0.0f, 1.0f}};
    clearValues[1].depthStencil = {1.0f, 0};
    renderPassInfo.clearValueCount = static_cast<uint32_t>(clearValues.size());;
    renderPassInfo.pClearValues = clearValues.data();

    vkCmdBeginRenderPass(m_commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);

    vkCmdBindPipeline(m_commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipeline.GetPipeline());

    VkViewport viewport{};
    viewport.x = 0.0f;
    viewport.y = 0.0f;
    viewport.width = static_cast<float>(m_width);
    viewport.height = static_cast<float>(m_height);
    viewport.minDepth = 0.0f;
    viewport.maxDepth = 1.0f;
    vkCmdSetViewport(m_commandBuffer, 0, 1, &viewport);

    VkRect2D scissor{};
    scissor.offset = {0, 0};
    scissor.extent = VkExtent2D{m_width, m_height};;
    vkCmdSetScissor(m_commandBuffer, 0, 1, &scissor);

    m_vkNodes.clear();
    for (auto model : m_models)
    {
        CreateVkNodeByPart(model->m_rootPart);
    }

    for (auto vkNode : m_vkNodes)
    {
        RecordOnVkNodeCommandBuffer(vkNode);
    }

    vkCmdEndRenderPass(m_commandBuffer);

    if (vkEndCommandBuffer(m_commandBuffer) != VK_SUCCESS) {
        throw std::runtime_error("failed to record command buffer!");
    }
}

void VulkanRenderer::CreateAndMapUniformBuffer()
{
    VkDeviceSize bufferSize = sizeof(ModelViewProj);

    MemoryTool::CreateBuffer(
        m_selectedPhysicalDevice->GetHandle(),
        m_device,
        bufferSize,
        VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
        VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
        m_uniformBuffer,
        m_uniformBuffersMemory);

    vkMapMemory(m_device, m_uniformBuffersMemory, 0, bufferSize, 0, &m_uniformBufferMapped);
}

void VulkanRenderer::UpdateUniformBuffer(float deltaSeconds)
{
    static auto startTime = std::chrono::high_resolution_clock::now();

    auto currentTime = std::chrono::high_resolution_clock::now();
    float time = std::chrono::duration<float, std::chrono::seconds::period>(currentTime - startTime).count();

    m_camera.UpdatePositon(deltaSeconds);

    ModelViewProj mvp{};
    mvp.model = glm::rotate(glm::mat4(1.0f), glm::radians(-90.0f), glm::vec3(1.0f, 0.0f, 0.0f));
    mvp.view = glm::lookAt(m_camera.m_posistion, m_camera.m_posistion + m_camera.m_face, m_camera.m_up);
    mvp.proj = glm::perspective(glm::radians(45.0f), m_width / (float) m_height, 0.1f, 10.0f);

    memcpy(m_uniformBufferMapped, &mvp, sizeof(mvp));
}

void VulkanRenderer::CreateDescriptorPool() 
{
    std::array<VkDescriptorPoolSize, 2> poolSizes{};
    poolSizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
    poolSizes[0].descriptorCount = 1;
    poolSizes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
    poolSizes[1].descriptorCount = 1;

    VkDescriptorPoolCreateInfo poolInfo{};
    poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
    poolInfo.poolSizeCount = static_cast<uint32_t>(poolSizes.size());
    poolInfo.pPoolSizes = poolSizes.data();
    poolInfo.maxSets = 1;

    //https://registry.khronos.org/vulkan/specs/latest/html/vkspec.html#vkCreateDescriptorPool
    if (vkCreateDescriptorPool(m_device, &poolInfo, nullptr, &m_descriptorPool) != VK_SUCCESS) {
        throw std::runtime_error("failed to create descriptor pool!");
    }
}

void VulkanRenderer::CreateDescriptorSets()
{
    std::vector<VkDescriptorSetLayout> layouts(1, m_pipeline.GetDescriptorSetLayout());
    VkDescriptorSetAllocateInfo allocInfo{};
    allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
    allocInfo.descriptorPool = m_descriptorPool;
    allocInfo.descriptorSetCount = 1;
    allocInfo.pSetLayouts = layouts.data();

    if (vkAllocateDescriptorSets(m_device, &allocInfo, &m_descriptorSet) != VK_SUCCESS)
    {
        throw std::runtime_error("failed to allocate descriptor sets!");
    };

    VkDescriptorBufferInfo bufferInfo{};
    bufferInfo.buffer = m_uniformBuffer;
    bufferInfo.offset = 0;
    bufferInfo.range = sizeof(ModelViewProj);

    VkDescriptorImageInfo imageInfo{};
    imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
    imageInfo.imageView = m_texture->GetImageView();
    imageInfo.sampler = m_texture->GetSampler();

    std::array<VkWriteDescriptorSet, 2> descriptorWrites{};

    descriptorWrites[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
    descriptorWrites[0].dstSet = m_descriptorSet;
    descriptorWrites[0].dstBinding = 0;
    descriptorWrites[0].dstArrayElement = 0;
    descriptorWrites[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
    descriptorWrites[0].descriptorCount = 1;
    descriptorWrites[0].pBufferInfo = &bufferInfo;

    descriptorWrites[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
    descriptorWrites[1].dstSet = m_descriptorSet;
    descriptorWrites[1].dstBinding = 1;
    descriptorWrites[1].dstArrayElement = 0;
    descriptorWrites[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
    descriptorWrites[1].descriptorCount = 1;
    descriptorWrites[1].pImageInfo = &imageInfo;

    vkUpdateDescriptorSets(m_device, static_cast<uint32_t>(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr);
}

void VulkanRenderer::CreateDepthResources()
{
    VkFormat depthFormat = FindDepthFormat();

    MemoryTool::CreateImage(
        m_selectedPhysicalDevice->GetHandle(),
        m_device,
        m_width,
        m_height,
        depthFormat,
        VK_IMAGE_TILING_OPTIMAL,
        VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT,
        VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
        m_depthImage,
        m_depthImageMemory);

    m_depthImageView = MemoryTool::CreateImageView(m_device, m_depthImage, depthFormat, VK_IMAGE_ASPECT_DEPTH_BIT);
}

VkFormat VulkanRenderer::FindDepthFormat()
{
    return FindSupportedFormat(
        {
            VK_FORMAT_D32_SFLOAT,
            VK_FORMAT_D32_SFLOAT_S8_UINT,
            VK_FORMAT_D24_UNORM_S8_UINT
        },
        VK_IMAGE_TILING_OPTIMAL,
        VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT
    );
}

VkFormat VulkanRenderer::FindSupportedFormat(const std::vector<VkFormat>& candidates, VkImageTiling tiling, VkFormatFeatureFlags features)
{
    for (VkFormat format : candidates) {
        VkFormatProperties props;
        vkGetPhysicalDeviceFormatProperties(m_selectedPhysicalDevice->GetHandle(), format, &props);

        if (tiling == VK_IMAGE_TILING_LINEAR && (props.linearTilingFeatures & features) == features) {
            return format;
        } else if (tiling == VK_IMAGE_TILING_OPTIMAL && (props.optimalTilingFeatures & features) == features) {
            return format;
        }
    }

    throw std::runtime_error("failed to find supported format!");
}



}; /* namespace namespace_easy_car_ui */